#include "ldasm.h"

static unsigned char packed_table[] = 
{
	0x80, 0x84, 0x80, 0x84, 0x80, 0x84, 0x80, 0x84,
	0x80, 0x88, 0x80, 0x88, 0x80, 0x88, 0x80, 0x88,
	0x8c, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
	0x90, 0x94, 0x98, 0x8b, 0x9c, 0x9c, 0x9c, 0x9c,
	0xa0, 0x80, 0x80, 0x80, 0x8b, 0x8b, 0xa4, 0x8b,
	0xa8, 0x8b, 0x84, 0x8b, 0xac, 0xac, 0xa8, 0xa8,
	0xb0, 0xb4, 0xb8, 0xbc, 0x80, 0xc0, 0x80, 0x80,
	0x9c, 0xac, 0xc4, 0x8b, 0xc8, 0x90, 0x8b, 0x90,
	0x80, 0x8b, 0x8b, 0xcc, 0x80, 0x80, 0xd0, 0x8b,
	0x80, 0xd4, 0x80, 0x80, 0x8b, 0x8b, 0x8b, 0x8b,
	0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
	0x80, 0x80, 0x80, 0x80, 0xd8, 0xdc, 0x8b, 0x80,
	0xe0, 0xe0, 0xe0, 0xe0, 0x80, 0x80, 0x80, 0x80,
	0x8f, 0xcf, 0x8f, 0xdb, 0x80, 0x80, 0xe4, 0x80,
	0xe8, 0xd9, 0x8b, 0x8b, 0x80, 0x80, 0x80, 0x80,
	0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xdc,
	0x08, 0x08, 0x08, 0x08, 0x01, 0x10, 0x00, 0x00,
	0x01, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x08, 0x08, 0x20, 0x20, 0x20, 0x20,
	0x10, 0x18, 0x01, 0x09, 0x81, 0x81, 0x81, 0x81,
	0x09, 0x18, 0x09, 0x09, 0x00, 0x00, 0x12, 0x00,
	0x10, 0x10, 0x10, 0x10, 0x01, 0x01, 0x01, 0x01,
	0x09, 0x09, 0x02, 0x00, 0x08, 0x08, 0x09, 0x18,
	0x03, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00,
	0x01, 0x01, 0x00, 0x00, 0x50, 0x50, 0x12, 0x81,
	0x20, 0x00, 0x20, 0x20, 0x00, 0x08, 0x00, 0x09,
	0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00,
	0x09, 0x09, 0x09, 0x09, 0x08, 0x08, 0x08, 0x00,
	0x50, 0x50, 0x50, 0x50, 0x00, 0x00, 0x09, 0x08,
	0x08, 0x08, 0x09, 0x08
};

static
unsigned long code_flags(unsigned long code)
{
	return packed_table[ 
		packed_table[ code / 4 ] + (code % 4) ];
} 

unsigned long x_code_flags(unsigned char *addr)
{
	unsigned long opcode;

	if ( (opcode = addr[0]) == 0x0F)
	{
		opcode = (addr[1] | OP_EXTENDED);
	}

	return code_flags(opcode) | (opcode & OP_EXTENDED);
}

unsigned long size_of_code(unsigned char *code, unsigned char **opcd)
{
	unsigned char i_mod, i_rm, i_reg;
	unsigned long op1, op2, flags;
	unsigned long pfx66, pfx67;
	unsigned long osize, oflen;

	pfx66 = pfx67 = osize = oflen = 0;

	/* skip preffixes */
	while (code_flags(*code) & OP_PREFIX)
	{
		if (*code == 0x66) pfx66 = 1;
		if (*code == 0x67) pfx67 = 1;
		code++; osize++;
	}

	/* get opcode size and flags */
	*opcd = code; op1 = *code++; osize++;

	if (op1 == 0x0F)
	{
		op2 = (*code | OP_EXTENDED);
		code++; osize++;		
	} else 
	{
		op2 = op1;
		
		/* pfx66 = pfx67 for opcodes A0 - A3 */
		if (op2 >= 0xA0 && op2 <= 0xA3) 
		{
			pfx66 = pfx67;
		}
	}

	flags = code_flags(op2);
	
	/* process MODRM byte */
	if (flags & OP_MODRM)
	{
		i_mod = (*code >> 6);
		i_reg = (*code & 0x38) >> 3; 
		i_rm  = (*code & 7);
		code++; osize++;

		/* in F6 and F7 opcodes, immediate value present if i_reg == 0 */
		if (op1 == 0xF6 && i_reg == 0) 
		{
			flags |= OP_DATA_I8;    
		}
		if (op1 == 0xF7 && i_reg == 0)
		{
			flags |= OP_DATA_PRE66_67; 
		}
			
		switch (i_mod)
		{
			case 0:
			  {
				  if (pfx67)
				  {
					  if (i_rm == 6) oflen = 2;
				  } else
				  {
					  if (i_rm == 5) oflen = 4;
				  }
			  }
			break;
			case 1:
			  {
				  oflen = 1;
			  }
		    break; 
			case 2: 
			  {
				  if (pfx67) oflen = 2; else oflen = 4;
			  }
			break;
		}
		
		/* process SIB byte */
		if (pfx67 == 0 && i_rm == 4 && i_mod != 3)
		{
			if ( (*code & 7) == 5 && (i_mod != 1) )
			{
				oflen = 4;
			}

			oflen++;
		}

		osize += oflen;
	}

	/* process offset */
	if (flags & OP_DATA_PRE66_67)
	{
		osize += 4 - (pfx66 << 1);
	}
	/* process immediate value */
	osize += (flags & 7);
	
	return osize;
}
